
Análisis Exploratorio de datos (EDA)
El análisis exploratorio, EDA (exploratory data analysis), se trata de entender los datos por medio de transformaciones y visualización de los mismos. En este análisis lo primero que surgen son preguntas que queremos responder por medio de los datos, la transformación y de ellos y las visualizaciones nos ayudarán a resolver esos interrogantes y dará pie a nuevos interrogantes y conclusiones.
Este proceso es más de investigación, de entender el problema para luego entender los datos, ¿Qué pasó? ¿Cómo pasó? ¿Por qué pasó? Este hace parte de la analítica descriptiva y diagnóstica.
El EDA es una parte muy importante de cualquier análisis, así tus respuestas sean muy obvias, pues siempre tendrás que examinar la calidad de tus datos. La limpieza de datos es una aplicación del EDA: se genera una pregunta acerca de si tus datos cumplen con tus expectativas o no. Para limpiar tus datos tendrás que desplegar todas las herramientas del EDA: visualización, transformación y modelado.
Para hacer este análisis en R, utilizaremos principalmente dos paqueterías:
tidyverse, dentro de este paquete usaremos principalmente los paquetes dplyr para la transformación y ggplot2 para visualización; y
DataExplorer, este es un paquete que nos ayuda a generar visualizaciones de forma automática y nos ayuda a entender los datos (varianza, missing values, distribuciones, correlaciones, dispersiones, entre otros). Recuerda instalar tu paquete con la función install.packages("DataExplorer").
Para poder iniciar, primero debemos cargar las librerías y los datos, estos últimos los tomaremos de la librería de datos:
library(tidyverse)
library(DataExplorer)
library(datos)
Antes de comenzar con el análisis, es importantes tener en cuenta algunas definiciones:
Una variable es una cantidad, cualidad o característica mesurable, es decir, que se puede medir (una columna del dataset).
Un valor es el estado de la variable en el momento en que fue medida. El valor de una variable puede cambiar de una medición a otra (registro).
Una observación es un conjunto de mediciones realizadas en condiciones similares (usualmente todas las mediciones de una observación son realizadas al mismo tiempo y sobre el mismo objeto). Una observación contiene muchos valores, cada uno asociado a una variable diferente. En algunas ocasiones nos referiremos a una observación como un punto específico (data point).
Los datos tabulares (tabular data) son un conjunto de valores, cada uno asociado a una variable y a una observación. Los datos tabulares están ordenados si cada valor está almacenado en su propia “celda”, cada variable cuenta con su propia columna, y cada observación corresponde a una fila.
Variación
La variación es la tendencia de valores de una variable a cambiar de una medición a otra. Las variables numéricas, nos dan indicios de cómo se comporta ella misma o como varía dependiendo de su distribución. Las variables categóricas también pueden variar si realizas mediciones con diferentes sujetos. La variación de cada variable sigue un patrón específico y este puede revelar información interesante. La mejor manera de entender dicho patrón es visualizando la distribución de los valores de la variable.
Datos que utilizaremos (data = diamantes)
De la paquetería de datos, utilizaremos el dataset diamantes, para obtenerlos, así no este en nuestro Enviroment, se ve como una función del paquete datos:
Si queremos guardarlo en nuestro ambiente de trabajo, le asignamos un nombre:
data <- diamantes
summary(data)
precio quilate corte color claridad profundidad tabla
Min. : 326 Min. :0.2000 Regular : 1610 D: 6775 SI1 :13065 Min. :43.00 Min. :43.00
1st Qu.: 950 1st Qu.:0.4000 Bueno : 4906 E: 9797 VS2 :12258 1st Qu.:61.00 1st Qu.:56.00
Median : 2401 Median :0.7000 Muy bueno:12082 F: 9542 SI2 : 9194 Median :61.80 Median :57.00
Mean : 3933 Mean :0.7979 Premium :13791 G:11292 VS1 : 8171 Mean :61.75 Mean :57.46
3rd Qu.: 5324 3rd Qu.:1.0400 Ideal :21551 H: 8304 VVS2 : 5066 3rd Qu.:62.50 3rd Qu.:59.00
Max. :18823 Max. :5.0100 I: 5422 VVS1 : 3655 Max. :79.00 Max. :95.00
J: 2808 (Other): 2531
x y z
Min. : 0.000 Min. : 0.000 Min. : 0.000
1st Qu.: 4.710 1st Qu.: 4.720 1st Qu.: 2.910
Median : 5.700 Median : 5.710 Median : 3.530
Mean : 5.731 Mean : 5.735 Mean : 3.539
3rd Qu.: 6.540 3rd Qu.: 6.540 3rd Qu.: 4.040
Max. :10.740 Max. :58.900 Max. :31.800
Visualización de distribuciones
Para realizar la visualización de una variable categórica, lo realizamos por medio de un gráfico de barras. Suponiendo que queremos ver la distribución de la variable corte:
data %>% ggplot()+geom_bar(aes(x=corte))

La altura de las barras muestra cuántas observaciones corresponden a cada valor de x. Puedes calcular estos valores con dplyr::count()
diamantes %>% count(corte)
Una variable es continua si puede adoptar un set infinito de valores ordenados. Los números y fechas-horas son dos ejemplos de variables continuas. Para examinar la distribución de una variable continua, usamos un histograma de frecuencias por medio de la función geom_histogram() de ggplot2, por ejemplo, queremos ver la distribución de la variable quilate:
data %>% ggplot()+geom_histogram(aes(x=quilate))

Para cambiar la clase a una definida, usamos la opción binwidth =:
data %>% ggplot()+geom_histogram(aes(x=quilate),binwidth = 0.5)

Si quieres el cálculo manual se puede lograr con dos funciones: dplyr::count() y ggplot2::cut_width():
data %>% count(cut_width(quilate, 0.5))
Si la idea es realizar una visualización de varias distribuciones que dependa de una variable categórica, seguimos con la misma idea que veníamos de las visualizaciones en ggplot2. Por ejemplo queremos ver la distribución de la variable quilate y que sean varias dependiendo de la categoría corte y que en lugar de barras veamos líneas usando la función geom_freqpoly():
data %>% ggplot() + geom_freqpoly(aes(x=quilate, colour = corte), binwidth = 0.1)

Ahora que ya sabemos realizar algunas visualizaciones, deberíamos hacernos algunas preguntas: ¿qué debería buscar en las visualizaciones? ¿Y qué tipo de preguntas de seguimiento debería hacer? La clave para hacer buenas preguntas será confiar en la curiosidad (¿qué necesitamos saber de más?), así como en tu escepticismo (¿de qué manera podría ser esto engañoso?).
Algunas de las preguntas más frecuentes que nos hacemos en el momento de realizar el análisis exploratorio es acerca de los valores típicos, por ejemplo.
¿Cuáles valores son los más frecuentes, menos frecuentes, patrones raros? ¿Por qué?
Por ejemplo, para el histograma anterior, podríamos dividir mucho más la clase para ver cuales son los valores más usuales. Si usamos binwidth = 0.01, podemos observar un patrón entre los quilates. También como vemos que no hay quilates mayores a 3 podemos hacer un filtro de nuestros datos:
data %>% filter(quilate<3) %>% ggplot()+geom_histogram(aes(x=quilate), binwidth = 0.01)

Outliers
Los valores atípicos o outliers, son puntos que se salen del patrón de los demás datos. Algunas veces dichos valores atípicos son errores cometidos durante la ingesta de datos; otras veces sugieren nueva información. Cuando tienes una gran cantidad de datos, es difícil identificar los valores atípicos en un histograma o en un diagrama de cajas y bigotes. Para el caso de los datos de diamantes esto sucede para la variable y:
data %>% ggplot()+geom_histogram(aes(x=y), binwidth = 0.5)

Hay muchas observaciones con frecuencias de más de 12000 es por eso que no vemos los pocos datos que hay en valores lejanos de y. Para verlos tenemos dos opciones, la primera es usar la misma visualización pero poner el eje más corto:
data %>% ggplot()+geom_histogram(aes(x=y), binwidth = 0.5)+coord_cartesian(ylim=c(0,30))

la otra opción es hacer un diagrama de cajas y bigotes
data %>% ggplot() + geom_boxplot(aes(x=y))

Esto nos indica que hay tres valores inusuales: ~0, ~30 y ~60. Podemos filtrar nuestros datos y verlos en una tabla con dplyr:
outliers <- data %>% filter(y < 3 | y > 20) %>% arrange(y)
outliers
La variable y mide una de las tres dimensiones de estos diamantes en mm (milímetros). Sabemos que los diamantes no pueden tener una anchura de 0mm, así que estos valores deben ser incorrectos. Es un buen hábito repetir tu análisis con y sin los valores inusuales.
Missing values
Cuando tenemos outliers tenemos dos opciones, una es eliminar todo el registro y otra es solo eliminar el valor y convertirlo a NA. La primera opción muchas veces no es recomendable porque eliminamos información de otras variables que quizá no presentan el mismo problema.
Si deseas eliminar estos valores, lo podemos hacer por medio de un filtro:
data %>% filter(between(y,3,20))
Por otro lado, para eliminar solo el valor y convertirlo en NA, podemos hacerlo con la función mutate(), así:
data1 <- data %>% mutate(y = ifelse(y<3 | y >20, NA,y))
Realizando la visualización de las variables x contra y con la función ggplot(), nos va a aparecer una advertencia que no inlcuye dentro de sus gráficos valores con NA:
data1 %>% ggplot() + geom_point(aes(x,y))

Para eliminar esa alerta, define na.rm = TRUE en la función geom_poin():
data1 %>% ggplot() + geom_point(aes(x,y), na.rm = TRUE)

Covarianza
Si la variación describe el comportamiento dentro de una variable, la covariación describe el comportamiento entre variables.
Una variable categórica y una continua
En algunas ocasiones, queremos ver las distribuciones de una variable continua discriminada por las clases de una variable categórica. La visualización usando geom_freqpoly() no es tan útil para este tipo de comparación, pues la altura está dada por la cuenta total. Eso significa que si uno de los grupos es mucho más pequeño que los demás, será difícil ver las diferencias en altura.
data1 %>% ggplot()+ geom_freqpoly(aes(x=precio, colour=corte), binwidth = 500)

Para facilitar esta comparación necesitamos cambiar lo que se muestra en el eje vertical. En lugar de mostrar la cuenta total, mostraremos la densidad, que es lo mismo que la cuenta estandarizada de manera que el área bajo cada polígono es igual a uno.
data1 %>% ggplot()+geom_freqpoly(aes(x=precio, y=..density.., colour = corte),binwidth = 500)

Lo que nos está indicando esta visualización, es que al parecer, la calidad más baja la tienen los diamantes regulares y en medio el precio más alto.
Otra opción para mostrar una distribución de una variable continua es por medio de los diagramas de cajas y bigotes. En ggplot2 lo exploramos con la función geom_boxplot(). Para recordar su interpretación, la caja comprende desde el percentil 25 de la distribución hasta el percentil 75, distancia que se conoce como rango intercuartil. La línea que está dentro de la caja es la mediana o percentil 50. Por otro lados los bigotes se unen por medio de la cada finalizando en el valor extremo percentil 0 y percentil 100. Los puntos que se encuentran fuera del bigote son los outliers.

Exploremos los datos de diamantes entre el corte y el precio:
data1 %>% ggplot()+geom_boxplot(aes(x = corte, y=precio))

Si los nombres de tus variables son muy largos, geom_boxplot() funcionará mejor si giras el gráfico en 90°. Puedes hacer esto agregando coord_flip() (voltear coordenadas).
data1 %>% ggplot() + geom_boxplot(aes(x = corte, y=precio))+coord_flip()

Relación entre dos variables categóricas
Para relacionar dos variables categóricas, debemos encontrar un número que nos relacione el conteo entre ellas. Existen diferentes formas de hacerlo, una de ellas es con la función geom_count() de la paquetería de ggplot2. Donde el tamaño nos indica el conteo de las observaciones con la combinación de variables correspondiente.
data1 %>% ggplot() + geom_count(aes(x=corte, y =color))

Si lo queremos ver como una tabla de datos, usamos el paquete dplyr:
data1 %>% count(color,corte)
Otra opción es usar la visualización con la función geom_tile() y adaptar un fill o relleno con el conteo n del conteo previamente realizado.
data1 %>% count(color,corte) %>%
ggplot()+
geom_tile(aes(x=color,y=corte, fill= n))

Relación entre dos variables continuas
Ya habíamos visto cómo realizar diagramas de dispersión o scatterplots con la función geom_point() y vimos que podemos realizar también la tendencia son función geom_smooth(). Los diagramas de dispersión resultan menos útiles a medida los datos crecen, pues los puntos empiezan a superponerse y amontonarse en áreas oscuras uniformes, para esto podemos hacer un poco de transparencia con la función alpha
data1 %>% ggplot()+
geom_point(aes(x=quilate, y=precio), alpha = 1/100)

Otra solución es modificar el parámetro bin (contenedor, que en este caso alude a la idea de rango o unidad). Anteriormente usaste geom_histogram() y geom_freqpoly() para segmentar una variable en rangos de manera unidimensional. Ahora aprenderás a usar geom_bin2d() y geom_hex() para hacerlo en dos dimensiones.
g1 <- data1 %>% ggplot()+
geom_bin2d(aes(x=quilate, y=precio))
g2 <- data1 %>% ggplot()+
geom_hex(aes(x=quilate,y=precio))
ggstatsplot::combine_plots(
plotlist = list(g1,g2),
plotgrid.args = list(nrow = 1),
annotation.args = list(caption = "Iteso")
)

Otra opción es crear intervalos con una de las variables continuas de manera de que pueda ser tratada como una variable categórica. Luego, puedes usar alguna de las técnicas de visualización empleadas para representar la combinación de una variable categórica con una variable continua. Por ejemplo,
data1 %>% ggplot() +
geom_boxplot(aes(x = quilate, y = precio, group = cut_number(quilate, 20)))

Reporte de exploración de datos
También podemos realizar reportes completos con DataExplorer::create_report(). Para el siguiente ejemplo debemos instalar los paquetes DataExplorer y nycflights13 para los datos (install.packages("nycflights13")).
library(nycflights13)
library(DataExplorer)
En el paquete library(DataExplorer) hay 5 data frames:
airlines
airports
flights
planes
weather
Podemos visualizar su estructura de la siguiente forma:
data <- list(airlines, airports, flights, planes, weather)
plot_str(data)
Para tener un sólo dataset más robusto podemos fusionar las tablas por medio de la función merge()
final_data <- flights %>% merge(airlines, by= "carrier", all.x = TRUE) %>%
merge(planes, by = "tailnum", all.x = TRUE, suffixes = c("_flights", "_planes")) %>%
merge(airports, by.x = "origin", by.y = "faa", all.x = TRUE, suffixes = c("_carrier", "_origin")) %>%
merge(airports, by.x = "dest", by.y = "faa", all.x = TRUE, suffixes = c("_origin", "_dest"))
Análisis exploratorio de datos con la paquetería DataExplorer
Para conocer el conjunto de datos podemos realizar un summary() como lo hicimos en la sesión anterior o podemos utilizar la función introduce().
# De forma gráfica
plot_intro(final_data)

Debemos de notar algo en este conjunto de datos:
Sólo el 0.27% de las filas están completas,
tenemos 5.7% de observaciones faltantes, es decir, dado que solo tenemos 0.27% de las filas completas, solo hay 5.7% de observaciones faltantes del total.
Estos valores faltantes nos podrán general problemas para analizar los datos, veamos un poco los perfiles que faltan.
Valores faltantes (missing values)
Siempre, en todos los problemas reales, vamos a tener datos desordenados y con problemas. Para visualizar el perfil de los datos faltantes podemos utilizar la función plot_missing().

Si le gusta más tener la información en forma de tabla puede obtener su perfil por medio de la función profile_missing(final_data).
En la visualización anterior, podemos ver que la variable speed es la que en su mayoría le falta información, al parecer encontramos el culpable de que sólo el 0.27% de nuestras filas estén completas y probablemente esta variable no sea de mucha información. Por tanto la podemos eliminar de nuestro dataframe.
final_data <- drop_columns(final_data, "speed")
Distribuciones
La visualización de las distribuciones de frecuencia para todas las características discretas se puede realizar con la función plot_bar().
5 columns ignored with more than 50 categories.
dest: 105 categories
tailnum: 4044 categories
time_hour: 6936 categories
model: 128 categories
name: 102 categories


Tras una inspección detallada de la variable manuracturer, no es fácil identificar las siguientes características duplicadas:
AIRBUS and AIRBUS INDUSTRIE
CANADAIR and CANADAIR LTD
MCDONNELL DOUGLAS, MCDONNELL DOUGLAS AIRCRAFT CO and MCDONNELL DOUGLAS CORPORATION
Por tanto, debemos proceder a limpiar esta variable
final_data[which(final_data$manufacturer == "AIRBUS INDUSTRIE"),]$manufacturer <- "AIRBUS"
final_data[which(final_data$manufacturer == "CANADAIR LTD"),]$manufacturer <- "CANADAIR"
final_data[which(final_data$manufacturer %in% c("MCDONNELL DOUGLAS AIRCRAFT CO", "MCDONNELL DOUGLAS CORPORATION")),]$manufacturer <- "MCDONNELL DOUGLAS"
plot_bar(final_data$manufacturer)

Adicionalmente, las variables dst_origin, tzone_origin, year_flights y tz_origin contienen un solo valor, por lo que deberíamos proceder a eliminarla, ya que no nos proporciona información:
final_data <- drop_columns(final_data, c("dst_origin", "tzone_origin", "year_flights", "tz_origin"))
Con frecuencia, es muy beneficioso observar la distribución de frecuencia bivariada. Por ejemplo, para ver características discretas por arr_delay:
plot_bar(final_data, with = "arr_delay")
5 columns ignored with more than 50 categories.
dest: 105 categories
tailnum: 4044 categories
time_hour: 6936 categories
model: 128 categories
name: 102 categories

Nótese que la distribución resultante se ve bastante diferente de la distribución de frecuencias regular.
Puede optar por dividir por colores todas las frecuencias por una variable discreta, como por ejemplo origin:
plot_bar(final_data, by = "origin")
5 columns ignored with more than 50 categories.
dest: 105 categories
tailnum: 4044 categories
time_hour: 6936 categories
model: 128 categories
name: 102 categories

Histogramas
Para visualizar distribuciones de todas las variables continuas podemos utilizar la función plot_histogram():
plot_histogram(final_data)


También podemos observar que hay variables de fechas y horas que deben tratarse un poco más, por ejemplo, concentrando año, mes, día para formar una variable de fecha y/o agregar hora y minuto para formar la variable fecha_hora.
Otra parte que podemos limpiar, es por ejemplo la variable flight, ya que esa deberíamos tenerla como un factor, por ser un número de vuelo y numéricamente no tiene ningún significado:
final_data <- update_columns(final_data, "flight", as.factor)
También podemos remover las variables year_flights y tz_origin ya que sólo contienen un valor:
final_data <- drop_columns(final_data, c("year_flights", "tz_origin"))
Column 'year_flights' does not exist to removeColumn 'tz_origin' does not exist to remove
QQ plot
La gráfica Quantile-Quantile es una forma de visualizar la desviasión de una distribución de probabilidad específica.
Después de analizar estos gráficos, a menudo es beneficioso aplicar una transformación matemática (como logaritmo) para modelos como la regresión lineal. Para hacerlo, podemos usar la función plot_qq. De forma predeterminada, se compara con la distribución normal.
Nota: La función llevará mucho tiempo con muchas observaciones, por lo que puede optar por especificar un sampled_rows apropiado:
qq_data <- final_data[, c("arr_delay", "air_time", "distance", "seats")]
plot_qq(qq_data, sampled_rows = 1000L)

En el gráfico, air_time, distance y asientos parecen sesgados en ambas colas. Apliquemos una transformación logarítmica simple y grafiquemos de nuevo.
log_qq_data <- update_columns(qq_data, 2:4, function(x) log(x + 1))
plot_qq(log_qq_data[, 2:4], sampled_rows = 1000L)

Con esto obtener una mejor distribución. Si es necesario, también puede ver el gráfico QQ mediante otra función:
qq_data <- final_data[, c("name_origin", "arr_delay", "air_time", "distance", "seats")]
plot_qq(qq_data, by = "name_origin", sampled_rows = 1000L)

Correlation Analysis
Para visualizar el mapa de calor de la correlación de todas las variables que no tengan datos faltantes lo podemos realizar de la siguiente forma:
plot_correlation(na.omit(final_data), maxcat = 5L)
11 features with more than 5 categories ignored!
dest: 100 categories
tailnum: 3246 categories
carrier: 16 categories
flight: 3773 categories
time_hour: 6642 categories
name_carrier: 16 categories
manufacturer: 24 categories
model: 121 categories
engine: 6 categories
name: 100 categories
tzone_dest: 7 categories

También puede graficar variables sólo discretas/continuas de la siguiente forma:
plot_correlation(na.omit(final_data), type = "c")

plot_correlation(na.omit(final_data), type = "d")
7 features with more than 20 categories ignored!
dest: 100 categories
tailnum: 3246 categories
flight: 3773 categories
time_hour: 6642 categories
manufacturer: 24 categories
model: 121 categories
name: 100 categories

Principal Component Analysis (PCA)
El análisis de componentes principales (PCA, por sus siglas en inglés,) consiste en expresar un conjunto de variables en un conjunto de combinaciones lineales de factores no correlacionados entre sí, estos factores dando cuenta una fracción cada vez más débil de la variabilidad de los datos.
Este análisis lo podemos realizar directamente con la función plot_prcomp (na.omit (final_data)), pero PCA funcionará mejor si limpiamos los datos primero:
pca_df <- na.omit(final_data[, c("origin", "dep_delay", "arr_delay", "air_time", "year_planes", "seats")])
plot_prcomp(pca_df, variance_cap = 0.9, nrow = 2L, ncol = 2L)


Boxplots
Suponga que le gustaría construir un modelo para predecir los retrasos en las llegadas, puede visualizar la distribución de todas las características continuas en función de los retrasos en las llegadas con un diagrama de caja/bigotes (boxplot):
## Reduce data size for demo purpose
arr_delay_df <- final_data[, c("arr_delay", "month", "day", "hour", "minute", "dep_delay", "distance", "year_planes", "seats")]
## Call boxplot function
plot_boxplot(arr_delay_df, by = "arr_delay")

Entre todos los cambios sutiles en correlación con los retrasos en las llegadas, se puede detectar inmediatamente que los aviones con más de 300 asientos tienden a tener retrasos mucho más largos (16 ~ 21 horas). Ahora podemos profundizar más para verificar o generar más hipótesis.
Scatterplots
Para mirar las relaciones entre variables podemos visualizar scatterplots o diagramas de dispersión.
arr_delay_df2 <- final_data[, c("arr_delay", "dep_time", "dep_delay", "arr_time", "air_time", "distance", "year_planes", "seats")]
plot_scatterplot(arr_delay_df2, by = "arr_delay", sampled_rows = 1000L)

Para finalizar, si queremos que se haga un reporte automático, lo podemos realizar con la función create_report(), lo que nos dará como resultado un html con todo lo que corresponde a un análisis rápido de exploración de datos, pero ojo, es más conveniente irlo haciendo paso a paso para que puedas ir entendiendo y limpiando tus datos.
LS0tCnRpdGxlOiAnUiBwYXJhIERhdGEgU2NpZW5jZScKYXV0aG9yOiAnUHJvZmVzb3JhOiBEcmEuIERpYW5hIFBhb2xhIE1vbnRveWEgRXNjb2JhciwgZGlhbmEubW9udG95YUBpdGVzby5teCcKZGF0ZTogIkVuZXJvIDIwMjIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0aGVtZTogY29zbW8KICAgIGhpZ2hsaWdodDogdGFuZ28KICBnaXRodWJfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgZGV2OiBqcGVnCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBkZl9wcmludDogcGFnZWQKc3VidGl0bGU6ICdBbmFsaXNpcyBFeHBsb3JhdG9yaW8nCi0tLQpgYGB7ciBzZXR1cCwgZWNobyA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobz0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSA3KQpgYGAKCjxzdHlsZT4KLmZvcmNlQnJlYWsgeyAtd2Via2l0LWNvbHVtbi1icmVhay1hZnRlcjogYWx3YXlzOyBicmVhay1hZnRlcjogY29sdW1uOyB9Cjwvc3R5bGU+Cgo8Y2VudGVyPgohW10oaHR0cHM6Ly9ib29rZG93bi5vcmcvQkVTVC9EU0ZBL2ltYWdlcy9SbG9nby5wbmcpe3dpZHRoPTEwJX0KICAhW10oaHR0cDovL29jaTAyLmltZy5pdGVzby5teC9pZGVudGlkYWRfZGVfaW5zdGFuY2lhXzIwMTgvSVRFU08vTG9nb3MlMjBJVEVTTy9Mb2dvLUlURVNPLVByaW5jaXBhbC5qcGcpe3dpZHRoPTM1JX0KCgo8L2NlbnRlcj4KCiMgQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyBkZSBkYXRvcyAoRURBKQoKRWwgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbywgRURBIChleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzKSwgc2UgdHJhdGEgZGUgZW50ZW5kZXIgbG9zIGRhdG9zIHBvciBtZWRpbyBkZSB0cmFuc2Zvcm1hY2lvbmVzIHkgdmlzdWFsaXphY2nDs24gZGUgbG9zIG1pc21vcy4gIEVuIGVzdGUgYW7DoWxpc2lzIGxvIHByaW1lcm8gcXVlIHN1cmdlbiBzb24gcHJlZ3VudGFzIHF1ZSBxdWVyZW1vcyByZXNwb25kZXIgcG9yIG1lZGlvIGRlIGxvcyBkYXRvcywgbGEgdHJhbnNmb3JtYWNpw7NuIHkgZGUgZWxsb3MgeSBsYXMgdmlzdWFsaXphY2lvbmVzIG5vcyBheXVkYXLDoW4gYSByZXNvbHZlciBlc29zIGludGVycm9nYW50ZXMgeSBkYXLDoSBwaWUgYSBudWV2b3MgaW50ZXJyb2dhbnRlcyB5IGNvbmNsdXNpb25lcy4KCkVzdGUgcHJvY2VzbyBlcyBtw6FzIGRlIGludmVzdGlnYWNpw7NuLCBkZSBlbnRlbmRlciBlbCBwcm9ibGVtYSBwYXJhIGx1ZWdvIGVudGVuZGVyIGxvcyBkYXRvcywgwr9RdcOpIHBhc8OzPyDCv0PDs21vIHBhc8OzPyDCv1BvciBxdcOpIHBhc8OzPyBFc3RlIGhhY2UgcGFydGUgZGUgbGEgYW5hbMOtdGljYSBkZXNjcmlwdGl2YSB5IGRpYWduw7NzdGljYS4gCgpFbCBFREEgZXMgdW5hIHBhcnRlIG11eSBpbXBvcnRhbnRlIGRlIGN1YWxxdWllciBhbsOhbGlzaXMsIGFzw60gdHVzIHJlc3B1ZXN0YXMgc2VhbiBtdXkgb2J2aWFzLCBwdWVzIHNpZW1wcmUgdGVuZHLDoXMgcXVlIGV4YW1pbmFyIGxhIGNhbGlkYWQgZGUgdHVzIGRhdG9zLiBMYSBsaW1waWV6YSBkZSBkYXRvcyBlcyB1bmEgYXBsaWNhY2nDs24gZGVsIEVEQTogc2UgZ2VuZXJhIHVuYSBwcmVndW50YSBhY2VyY2EgZGUgc2kgdHVzIGRhdG9zIGN1bXBsZW4gY29uIHR1cyBleHBlY3RhdGl2YXMgbyBuby4gUGFyYSBsaW1waWFyIHR1cyBkYXRvcyB0ZW5kcsOhcyBxdWUgZGVzcGxlZ2FyIHRvZGFzIGxhcyBoZXJyYW1pZW50YXMgZGVsIEVEQTogdmlzdWFsaXphY2nDs24sIHRyYW5zZm9ybWFjacOzbiB5IG1vZGVsYWRvLgoKUGFyYSBoYWNlciBlc3RlIGFuw6FsaXNpcyBlbiBSLCB1dGlsaXphcmVtb3MgcHJpbmNpcGFsbWVudGUgZG9zIHBhcXVldGVyw61hczogCgotIGB0aWR5dmVyc2VgLCBkZW50cm8gZGUgZXN0ZSBwYXF1ZXRlIHVzYXJlbW9zIHByaW5jaXBhbG1lbnRlIGxvcyBwYXF1ZXRlcyBgZHBseXJgIHBhcmEgbGEgdHJhbnNmb3JtYWNpw7NuIHkgYGdncGxvdDJgIHBhcmEgdmlzdWFsaXphY2nDs247IHkgCgotIGBEYXRhRXhwbG9yZXJgLCBlc3RlIGVzIHVuIHBhcXVldGUgcXVlIG5vcyBheXVkYSBhIGdlbmVyYXIgdmlzdWFsaXphY2lvbmVzIGRlIGZvcm1hIGF1dG9tw6F0aWNhIHkgbm9zIGF5dWRhIGEgZW50ZW5kZXIgbG9zIGRhdG9zICh2YXJpYW56YSwgbWlzc2luZyB2YWx1ZXMsIGRpc3RyaWJ1Y2lvbmVzLCBjb3JyZWxhY2lvbmVzLCBkaXNwZXJzaW9uZXMsIGVudHJlIG90cm9zKS4gUmVjdWVyZGEgaW5zdGFsYXIgdHUgcGFxdWV0ZSBjb24gbGEgZnVuY2nDs24gYGluc3RhbGwucGFja2FnZXMoIkRhdGFFeHBsb3JlciIpYC4KClBhcmEgcG9kZXIgaW5pY2lhciwgcHJpbWVybyBkZWJlbW9zIGNhcmdhciBsYXMgbGlicmVyw61hcyB5IGxvcyBkYXRvcywgZXN0b3Mgw7psdGltb3MgbG9zIHRvbWFyZW1vcyBkZSBsYSBsaWJyZXLDrWEgZGUgYGRhdG9zYDoKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEYXRhRXhwbG9yZXIpCmxpYnJhcnkoZGF0b3MpCmBgYAoKQW50ZXMgZGUgY29tZW56YXIgY29uIGVsIGFuw6FsaXNpcywgZXMgaW1wb3J0YW50ZXMgdGVuZXIgZW4gY3VlbnRhIGFsZ3VuYXMgZGVmaW5pY2lvbmVzOgoKLSAqVW5hIHZhcmlhYmxlKiBlcyB1bmEgY2FudGlkYWQsIGN1YWxpZGFkIG8gY2FyYWN0ZXLDrXN0aWNhIG1lc3VyYWJsZSwgZXMgZGVjaXIsIHF1ZSBzZSBwdWVkZSBtZWRpciAodW5hIGNvbHVtbmEgZGVsIGRhdGFzZXQpLgoKLSAqVW4gdmFsb3IqIGVzIGVsIGVzdGFkbyBkZSBsYSB2YXJpYWJsZSBlbiBlbCBtb21lbnRvIGVuIHF1ZSBmdWUgbWVkaWRhLiBFbCB2YWxvciBkZSB1bmEgdmFyaWFibGUgcHVlZGUgY2FtYmlhciBkZSB1bmEgbWVkaWNpw7NuIGEgb3RyYSAocmVnaXN0cm8pLgoKLSAqVW5hIG9ic2VydmFjacOzbiogZXMgdW4gY29uanVudG8gZGUgbWVkaWNpb25lcyByZWFsaXphZGFzIGVuIGNvbmRpY2lvbmVzIHNpbWlsYXJlcyAodXN1YWxtZW50ZSB0b2RhcyBsYXMgbWVkaWNpb25lcyBkZSB1bmEgb2JzZXJ2YWNpw7NuIHNvbiByZWFsaXphZGFzIGFsIG1pc21vIHRpZW1wbyB5IHNvYnJlIGVsIG1pc21vIG9iamV0bykuIFVuYSBvYnNlcnZhY2nDs24gY29udGllbmUgbXVjaG9zIHZhbG9yZXMsIGNhZGEgdW5vIGFzb2NpYWRvIGEgdW5hIHZhcmlhYmxlIGRpZmVyZW50ZS4gRW4gYWxndW5hcyBvY2FzaW9uZXMgbm9zIHJlZmVyaXJlbW9zIGEgdW5hIG9ic2VydmFjacOzbiBjb21vIHVuIHB1bnRvIGVzcGVjw61maWNvIChkYXRhIHBvaW50KS4KCi0gKkxvcyBkYXRvcyB0YWJ1bGFyZXMqICh0YWJ1bGFyIGRhdGEpIHNvbiB1biBjb25qdW50byBkZSB2YWxvcmVzLCBjYWRhIHVubyBhc29jaWFkbyBhIHVuYSB2YXJpYWJsZSB5IGEgdW5hIG9ic2VydmFjacOzbi4gTG9zIGRhdG9zIHRhYnVsYXJlcyBlc3TDoW4gb3JkZW5hZG9zIHNpIGNhZGEgdmFsb3IgZXN0w6EgYWxtYWNlbmFkbyBlbiBzdSBwcm9waWEg4oCcY2VsZGHigJ0sIGNhZGEgdmFyaWFibGUgY3VlbnRhIGNvbiBzdSBwcm9waWEgY29sdW1uYSwgeSBjYWRhIG9ic2VydmFjacOzbiBjb3JyZXNwb25kZSBhIHVuYSBmaWxhLgoKIyMgVmFyaWFjacOzbgoKTGEgKnZhcmlhY2nDs24qIGVzIGxhIHRlbmRlbmNpYSBkZSB2YWxvcmVzIGRlIHVuYSB2YXJpYWJsZSBhIGNhbWJpYXIgZGUgdW5hIG1lZGljacOzbiBhIG90cmEuIExhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcywgbm9zIGRhbiBpbmRpY2lvcyBkZSBjw7NtbyBzZSBjb21wb3J0YSBlbGxhIG1pc21hIG8gY29tbyB2YXLDrWEgZGVwZW5kaWVuZG8gZGUgc3UgZGlzdHJpYnVjacOzbi4gTGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgdGFtYmnDqW4gcHVlZGVuIHZhcmlhciBzaSByZWFsaXphcyBtZWRpY2lvbmVzIGNvbiBkaWZlcmVudGVzIHN1amV0b3MuIExhIHZhcmlhY2nDs24gZGUgY2FkYSB2YXJpYWJsZSBzaWd1ZSB1biBwYXRyw7NuIGVzcGVjw61maWNvIHkgZXN0ZSBwdWVkZSByZXZlbGFyIGluZm9ybWFjacOzbiBpbnRlcmVzYW50ZS4gTGEgbWVqb3IgbWFuZXJhIGRlIGVudGVuZGVyIGRpY2hvIHBhdHLDs24gZXMgdmlzdWFsaXphbmRvIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIHZhbG9yZXMgZGUgbGEgdmFyaWFibGUuCgojIyMgRGF0b3MgcXVlIHV0aWxpemFyZW1vcyAoYGRhdGEgPSBkaWFtYW50ZXNgKQoKRGUgbGEgcGFxdWV0ZXLDrWEgZGUgYGRhdG9zYCwgdXRpbGl6YXJlbW9zIGVsIGRhdGFzZXQgYGRpYW1hbnRlc2AsIHBhcmEgb2J0ZW5lcmxvcywgYXPDrSBubyBlc3RlIGVuIG51ZXN0cm8gRW52aXJvbWVudCwgc2UgdmUgY29tbyB1bmEgZnVuY2nDs24gZGVsIHBhcXVldGUgYGRhdG9zYDoKYGBge3J9CmRpYW1hbnRlcwpgYGAKClNpIHF1ZXJlbW9zIGd1YXJkYXJsbyBlbiBudWVzdHJvIGFtYmllbnRlIGRlIHRyYWJham8sIGxlIGFzaWduYW1vcyB1biBub21icmU6CmBgYHtyfQpkYXRhIDwtIGRpYW1hbnRlcwpzdW1tYXJ5KGRhdGEpCmBgYAoKIyMjIFZpc3VhbGl6YWNpw7NuIGRlIGRpc3RyaWJ1Y2lvbmVzCgpQYXJhIHJlYWxpemFyIGxhIHZpc3VhbGl6YWNpw7NuIGRlIHVuYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSwgbG8gcmVhbGl6YW1vcyBwb3IgbWVkaW8gZGUgdW4gZ3LDoWZpY28gZGUgYmFycmFzLiBTdXBvbmllbmRvIHF1ZSBxdWVyZW1vcyB2ZXIgbGEgZGlzdHJpYnVjacOzbiBkZSBsYSB2YXJpYWJsZSBgY29ydGVgOiAKCmBgYHtyfQpkYXRhICU+JSBnZ3Bsb3QoKStnZW9tX2JhcihhZXMoeD1jb3J0ZSkpCmBgYAoKTGEgYWx0dXJhIGRlIGxhcyBiYXJyYXMgbXVlc3RyYSBjdcOhbnRhcyBvYnNlcnZhY2lvbmVzIGNvcnJlc3BvbmRlbiBhIGNhZGEgdmFsb3IgZGUgeC4gUHVlZGVzIGNhbGN1bGFyIGVzdG9zIHZhbG9yZXMgY29uIGBkcGx5cjo6Y291bnQoKWAKCmBgYHtyfQpkaWFtYW50ZXMgJT4lIGNvdW50KGNvcnRlKQpgYGAKCgpVbmEgdmFyaWFibGUgZXMgY29udGludWEgc2kgcHVlZGUgYWRvcHRhciB1biBzZXQgaW5maW5pdG8gZGUgdmFsb3JlcyBvcmRlbmFkb3MuIExvcyBuw7ptZXJvcyB5IGZlY2hhcy1ob3JhcyBzb24gZG9zIGVqZW1wbG9zIGRlIHZhcmlhYmxlcyBjb250aW51YXMuIFBhcmEgZXhhbWluYXIgbGEgZGlzdHJpYnVjacOzbiBkZSB1bmEgdmFyaWFibGUgY29udGludWEsIHVzYW1vcyAgdW4gaGlzdG9ncmFtYSBkZSBmcmVjdWVuY2lhcyBwb3IgbWVkaW8gZGUgbGEgZnVuY2nDs24gYGdlb21faGlzdG9ncmFtKClgIGRlIGBnZ3Bsb3QyYCwgcG9yIGVqZW1wbG8sIHF1ZXJlbW9zIHZlciBsYSBkaXN0cmlidWNpw7NuIGRlIGxhIHZhcmlhYmxlIGBxdWlsYXRlYDoKCmBgYHtyfQpkYXRhICU+JSBnZ3Bsb3QoKStnZW9tX2hpc3RvZ3JhbShhZXMoeD1xdWlsYXRlKSkKYGBgCgpQYXJhIGNhbWJpYXIgbGEgY2xhc2UgYSB1bmEgZGVmaW5pZGEsIHVzYW1vcyBsYSBvcGNpw7NuIGBiaW53aWR0aCA9YDoKCmBgYHtyfQpkYXRhICU+JSBnZ3Bsb3QoKStnZW9tX2hpc3RvZ3JhbShhZXMoeD1xdWlsYXRlKSxiaW53aWR0aCA9IDAuNSkKYGBgCgpTaSBxdWllcmVzIGVsIGPDoWxjdWxvIG1hbnVhbCBzZSBwdWVkZSBsb2dyYXIgY29uIGRvcyBmdW5jaW9uZXM6IGBkcGx5cjo6Y291bnQoKWAgeSBgZ2dwbG90Mjo6Y3V0X3dpZHRoKClgOgpgYGB7cn0KZGF0YSAlPiUgY291bnQoY3V0X3dpZHRoKHF1aWxhdGUsIDAuNSkpCmBgYAoKU2kgbGEgaWRlYSBlcyByZWFsaXphciB1bmEgdmlzdWFsaXphY2nDs24gZGUgdmFyaWFzIGRpc3RyaWJ1Y2lvbmVzIHF1ZSBkZXBlbmRhIGRlIHVuYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSwgc2VndWltb3MgY29uIGxhIG1pc21hIGlkZWEgcXVlIHZlbsOtYW1vcyBkZSBsYXMgdmlzdWFsaXphY2lvbmVzIGVuIGBnZ3Bsb3QyYC4gUG9yIGVqZW1wbG8gcXVlcmVtb3MgdmVyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGEgdmFyaWFibGUgYHF1aWxhdGVgIHkgcXVlIHNlYW4gdmFyaWFzIGRlcGVuZGllbmRvIGRlIGxhIGNhdGVnb3LDrWEgYGNvcnRlYCB5IHF1ZSBlbiBsdWdhciBkZSBiYXJyYXMgdmVhbW9zIGzDrW5lYXMgdXNhbmRvIGxhIGZ1bmNpw7NuIGBnZW9tX2ZyZXFwb2x5KClgOgpgYGB7cn0KZGF0YSAlPiUgZ2dwbG90KCkgKyBnZW9tX2ZyZXFwb2x5KGFlcyh4PXF1aWxhdGUsIGNvbG91ciA9IGNvcnRlKSwgYmlud2lkdGggPSAwLjEpCmBgYAoKQWhvcmEgcXVlIHlhIHNhYmVtb3MgcmVhbGl6YXIgYWxndW5hcyB2aXN1YWxpemFjaW9uZXMsIGRlYmVyw61hbW9zIGhhY2Vybm9zIGFsZ3VuYXMgcHJlZ3VudGFzOiDCv3F1w6kgZGViZXLDrWEgYnVzY2FyIGVuIGxhcyB2aXN1YWxpemFjaW9uZXM/IMK/WSBxdcOpIHRpcG8gZGUgcHJlZ3VudGFzIGRlIHNlZ3VpbWllbnRvIGRlYmVyw61hIGhhY2VyPyAgTGEgY2xhdmUgcGFyYSBoYWNlciBidWVuYXMgcHJlZ3VudGFzIHNlcsOhIGNvbmZpYXIgZW4gbGEgY3VyaW9zaWRhZCAowr9xdcOpIG5lY2VzaXRhbW9zIHNhYmVyIGRlIG3DoXM/KSwgYXPDrSBjb21vIGVuIHR1IGVzY2VwdGljaXNtbyAowr9kZSBxdcOpIG1hbmVyYSBwb2Ryw61hIHNlciBlc3RvIGVuZ2HDsW9zbz8pLgoKQWxndW5hcyBkZSBsYXMgcHJlZ3VudGFzIG3DoXMgZnJlY3VlbnRlcyBxdWUgbm9zIGhhY2Vtb3MgZW4gZWwgbW9tZW50byBkZSByZWFsaXphciBlbCBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGVzIGFjZXJjYSBkZSAqbG9zIHZhbG9yZXMgdMOtcGljb3MqLCBwb3IgZWplbXBsby4KCsK/Q3XDoWxlcyB2YWxvcmVzIHNvbiBsb3MgbcOhcyBmcmVjdWVudGVzLCBtZW5vcyBmcmVjdWVudGVzLCBwYXRyb25lcyByYXJvcz8gwr9Qb3IgcXXDqT8KClBvciBlamVtcGxvLCBwYXJhIGVsIGhpc3RvZ3JhbWEgYW50ZXJpb3IsIHBvZHLDrWFtb3MgZGl2aWRpciBtdWNobyBtw6FzIGxhIGNsYXNlIHBhcmEgdmVyIGN1YWxlcyBzb24gbG9zIHZhbG9yZXMgbcOhcyB1c3VhbGVzLiBTaSB1c2Ftb3MgYGJpbndpZHRoID0gMC4wMWAsIHBvZGVtb3Mgb2JzZXJ2YXIgdW4gcGF0csOzbiBlbnRyZSBsb3MgcXVpbGF0ZXMuIFRhbWJpw6luIGNvbW8gdmVtb3MgcXVlIG5vIGhheSBxdWlsYXRlcyBtYXlvcmVzIGEgMyBwb2RlbW9zIGhhY2VyIHVuIGZpbHRybyBkZSBudWVzdHJvcyBkYXRvczoKCmBgYHtyfQpkYXRhICU+JSBmaWx0ZXIocXVpbGF0ZTwzKSAlPiUgIGdncGxvdCgpK2dlb21faGlzdG9ncmFtKGFlcyh4PXF1aWxhdGUpLCBiaW53aWR0aCA9IDAuMDEpCmBgYAoKIyBPdXRsaWVycwoKTG9zIHZhbG9yZXMgYXTDrXBpY29zIG8gb3V0bGllcnMsIHNvbiBwdW50b3MgcXVlIHNlIHNhbGVuIGRlbCBwYXRyw7NuIGRlIGxvcyBkZW3DoXMgZGF0b3MuIEFsZ3VuYXMgdmVjZXMgZGljaG9zIHZhbG9yZXMgYXTDrXBpY29zIHNvbiBlcnJvcmVzIGNvbWV0aWRvcyBkdXJhbnRlIGxhIGluZ2VzdGEgZGUgZGF0b3M7IG90cmFzIHZlY2VzIHN1Z2llcmVuIG51ZXZhIGluZm9ybWFjacOzbi4gQ3VhbmRvIHRpZW5lcyB1bmEgZ3JhbiBjYW50aWRhZCBkZSBkYXRvcywgZXMgZGlmw61jaWwgaWRlbnRpZmljYXIgbG9zIHZhbG9yZXMgYXTDrXBpY29zIGVuIHVuIGhpc3RvZ3JhbWEgbyBlbiB1biBkaWFncmFtYSBkZSBjYWphcyB5IGJpZ290ZXMuIFBhcmEgZWwgY2FzbyBkZSBsb3MgZGF0b3MgZGUgYGRpYW1hbnRlc2AgZXN0byBzdWNlZGUgcGFyYSBsYSB2YXJpYWJsZSBgeWA6CmBgYHtyfQpkYXRhICU+JSBnZ3Bsb3QoKStnZW9tX2hpc3RvZ3JhbShhZXMoeD15KSwgYmlud2lkdGggPSAwLjUpCmBgYAoKSGF5IG11Y2hhcyBvYnNlcnZhY2lvbmVzIGNvbiBmcmVjdWVuY2lhcyBkZSBtw6FzIGRlIDEyMDAwIGVzIHBvciBlc28gcXVlIG5vIHZlbW9zIGxvcyBwb2NvcyBkYXRvcyBxdWUgaGF5IGVuIHZhbG9yZXMgbGVqYW5vcyBkZSB5LiBQYXJhIHZlcmxvcyB0ZW5lbW9zIGRvcyBvcGNpb25lcywgbGEgcHJpbWVyYSBlcyB1c2FyIGxhIG1pc21hIHZpc3VhbGl6YWNpw7NuIHBlcm8gcG9uZXIgZWwgZWplIG3DoXMgY29ydG86CmBgYHtyfQpkYXRhICU+JSBnZ3Bsb3QoKStnZW9tX2hpc3RvZ3JhbShhZXMoeD15KSwgYmlud2lkdGggPSAwLjUpK2Nvb3JkX2NhcnRlc2lhbih5bGltPWMoMCwzMCkpCmBgYAoKbGEgb3RyYSBvcGNpw7NuIGVzIGhhY2VyIHVuIGRpYWdyYW1hIGRlIGNhamFzIHkgYmlnb3RlcwpgYGB7cn0KZGF0YSAlPiUgZ2dwbG90KCkgKyBnZW9tX2JveHBsb3QoYWVzKHg9eSkpCmBgYAoKCkVzdG8gbm9zIGluZGljYSBxdWUgaGF5IHRyZXMgdmFsb3JlcyBpbnVzdWFsZXM6IH4wLCB+MzAgeSB+NjAuIFBvZGVtb3MgZmlsdHJhciBudWVzdHJvcyBkYXRvcyB5IHZlcmxvcyBlbiB1bmEgdGFibGEgY29uIGBkcGx5cmA6CmBgYHtyfQpvdXRsaWVycyA8LSBkYXRhICU+JSBmaWx0ZXIoeSA8IDMgfCB5ID4gMjApICU+JSBhcnJhbmdlKHkpCm91dGxpZXJzIApgYGAKCkxhIHZhcmlhYmxlIGB5YCBtaWRlIHVuYSBkZSBsYXMgdHJlcyBkaW1lbnNpb25lcyBkZSBlc3RvcyBkaWFtYW50ZXMgZW4gbW0gKG1pbMOtbWV0cm9zKS4gU2FiZW1vcyBxdWUgbG9zIGRpYW1hbnRlcyBubyBwdWVkZW4gdGVuZXIgdW5hIGFuY2h1cmEgZGUgMG1tLCBhc8OtIHF1ZSBlc3RvcyB2YWxvcmVzIGRlYmVuIHNlciBpbmNvcnJlY3Rvcy4gRXMgdW4gYnVlbiBow6FiaXRvIHJlcGV0aXIgdHUgYW7DoWxpc2lzIGNvbiB5IHNpbiBsb3MgdmFsb3JlcyBpbnVzdWFsZXMuCgojIE1pc3NpbmcgdmFsdWVzCgpDdWFuZG8gdGVuZW1vcyBvdXRsaWVycyB0ZW5lbW9zIGRvcyBvcGNpb25lcywgdW5hIGVzIGVsaW1pbmFyIHRvZG8gZWwgcmVnaXN0cm8geSBvdHJhIGVzIHNvbG8gZWxpbWluYXIgZWwgdmFsb3IgeSBjb252ZXJ0aXJsbyBhIGBOQWAuIExhIHByaW1lcmEgb3BjacOzbiBtdWNoYXMgdmVjZXMgbm8gZXMgcmVjb21lbmRhYmxlIHBvcnF1ZSBlbGltaW5hbW9zIGluZm9ybWFjacOzbiBkZSBvdHJhcyB2YXJpYWJsZXMgcXVlIHF1aXrDoSBubyBwcmVzZW50YW4gZWwgbWlzbW8gcHJvYmxlbWEuCgpTaSBkZXNlYXMgZWxpbWluYXIgZXN0b3MgdmFsb3JlcywgbG8gcG9kZW1vcyBoYWNlciBwb3IgbWVkaW8gZGUgdW4gZmlsdHJvOgpgYGB7cn0KZGF0YSAlPiUgZmlsdGVyKGJldHdlZW4oeSwzLDIwKSkKYGBgCgpQb3Igb3RybyBsYWRvLCBwYXJhIGVsaW1pbmFyIHNvbG8gZWwgdmFsb3IgeSBjb252ZXJ0aXJsbyBlbiBOQSwgcG9kZW1vcyBoYWNlcmxvIGNvbiBsYSBmdW5jacOzbiBgbXV0YXRlKClgLCBhc8OtOgpgYGB7cn0KZGF0YTEgPC0gZGF0YSAlPiUgbXV0YXRlKHkgPSBpZmVsc2UoeTwzIHwgeSA+MjAsIE5BLHkpKSAKYGBgCgpSZWFsaXphbmRvIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGxhcyB2YXJpYWJsZXMgYHhgIGNvbnRyYSBgeWAgY29uIGxhIGZ1bmNpw7NuIGBnZ3Bsb3QoKWAsIG5vcyB2YSBhIGFwYXJlY2VyIHVuYSBhZHZlcnRlbmNpYSBxdWUgbm8gaW5sY3V5ZSBkZW50cm8gZGUgc3VzIGdyw6FmaWNvcyB2YWxvcmVzIGNvbiBgTkFgOgpgYGB7cn0KZGF0YTEgJT4lIGdncGxvdCgpICsgZ2VvbV9wb2ludChhZXMoeCx5KSkKYGBgCgpQYXJhIGVsaW1pbmFyIGVzYSBhbGVydGEsIGRlZmluZSBgbmEucm0gPSBUUlVFYCBlbiBsYSBmdW5jacOzbiBgZ2VvbV9wb2luKClgOgpgYGB7cn0KZGF0YTEgJT4lIGdncGxvdCgpICsgZ2VvbV9wb2ludChhZXMoeCx5KSwgbmEucm0gPSBUUlVFKQpgYGAKCiMgQ292YXJpYW56YQoKU2kgbGEgdmFyaWFjacOzbiBkZXNjcmliZSBlbCBjb21wb3J0YW1pZW50byBkZW50cm8gZGUgdW5hIHZhcmlhYmxlLCBsYSBjb3ZhcmlhY2nDs24gZGVzY3JpYmUgZWwgY29tcG9ydGFtaWVudG8gZW50cmUgdmFyaWFibGVzLgoKIyMgVW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHkgdW5hIGNvbnRpbnVhCgpFbiBhbGd1bmFzIG9jYXNpb25lcywgcXVlcmVtb3MgdmVyIGxhcyBkaXN0cmlidWNpb25lcyBkZSB1bmEgdmFyaWFibGUgY29udGludWEgZGlzY3JpbWluYWRhIHBvciBsYXMgY2xhc2VzIGRlIHVuYSB2YXJpYWJsZSBjYXRlZ8OzcmljYS4gTGEgdmlzdWFsaXphY2nDs24gdXNhbmRvIGBnZW9tX2ZyZXFwb2x5KClgIG5vIGVzIHRhbiDDunRpbCBwYXJhIGVzdGUgdGlwbyBkZSBjb21wYXJhY2nDs24sIHB1ZXMgbGEgYWx0dXJhIGVzdMOhIGRhZGEgcG9yIGxhIGN1ZW50YSB0b3RhbC4gRXNvIHNpZ25pZmljYSBxdWUgc2kgdW5vIGRlIGxvcyBncnVwb3MgZXMgbXVjaG8gbcOhcyBwZXF1ZcOxbyBxdWUgbG9zIGRlbcOhcywgc2Vyw6EgZGlmw61jaWwgdmVyIGxhcyBkaWZlcmVuY2lhcyBlbiBhbHR1cmEuIAoKYGBge3J9CmRhdGExICU+JSBnZ3Bsb3QoKSsgZ2VvbV9mcmVxcG9seShhZXMoeD1wcmVjaW8sIGNvbG91cj1jb3J0ZSksIGJpbndpZHRoID0gNTAwKQpgYGAKClBhcmEgZmFjaWxpdGFyIGVzdGEgY29tcGFyYWNpw7NuIG5lY2VzaXRhbW9zIGNhbWJpYXIgbG8gcXVlIHNlIG11ZXN0cmEgZW4gZWwgZWplIHZlcnRpY2FsLiBFbiBsdWdhciBkZSBtb3N0cmFyIGxhIGN1ZW50YSB0b3RhbCwgbW9zdHJhcmVtb3MgbGEgZGVuc2lkYWQsIHF1ZSBlcyBsbyBtaXNtbyBxdWUgbGEgY3VlbnRhIGVzdGFuZGFyaXphZGEgZGUgbWFuZXJhIHF1ZSBlbCDDoXJlYSBiYWpvIGNhZGEgcG9sw61nb25vIGVzIGlndWFsIGEgdW5vLgoKYGBge3J9CmRhdGExICU+JSAgZ2dwbG90KCkrZ2VvbV9mcmVxcG9seShhZXMoeD1wcmVjaW8sIHk9Li5kZW5zaXR5Li4sIGNvbG91ciA9IGNvcnRlKSxiaW53aWR0aCA9IDUwMCkKYGBgCgpMbyBxdWUgbm9zIGVzdMOhIGluZGljYW5kbyBlc3RhIHZpc3VhbGl6YWNpw7NuLCBlcyBxdWUgYWwgcGFyZWNlciwgbGEgY2FsaWRhZCBtw6FzIGJhamEgbGEgdGllbmVuIGxvcyBkaWFtYW50ZXMgcmVndWxhcmVzIHkgZW4gbWVkaW8gZWwgcHJlY2lvIG3DoXMgYWx0by4gCgpPdHJhIG9wY2nDs24gcGFyYSBtb3N0cmFyIHVuYSBkaXN0cmlidWNpw7NuIGRlIHVuYSB2YXJpYWJsZSBjb250aW51YSBlcyBwb3IgbWVkaW8gZGUgbG9zIGRpYWdyYW1hcyBkZSBjYWphcyB5IGJpZ290ZXMuIEVuIGBnZ3Bsb3QyYCBsbyBleHBsb3JhbW9zIGNvbiBsYSBmdW5jacOzbiBgZ2VvbV9ib3hwbG90KClgLiBQYXJhIHJlY29yZGFyIHN1IGludGVycHJldGFjacOzbiwgbGEgY2FqYSBjb21wcmVuZGUgZGVzZGUgZWwgcGVyY2VudGlsIDI1IGRlIGxhIGRpc3RyaWJ1Y2nDs24gaGFzdGEgZWwgcGVyY2VudGlsIDc1LCBkaXN0YW5jaWEgcXVlIHNlIGNvbm9jZSBjb21vIHJhbmdvIGludGVyY3VhcnRpbC4gTGEgbMOtbmVhIHF1ZSBlc3TDoSBkZW50cm8gZGUgbGEgY2FqYSBlcyBsYSBtZWRpYW5hIG8gcGVyY2VudGlsIDUwLiAgUG9yIG90cm8gbGFkb3MgbG9zIGJpZ290ZXMgc2UgdW5lbiBwb3IgbWVkaW8gZGUgbGEgY2FkYSBmaW5hbGl6YW5kbyBlbiBlbCB2YWxvciBleHRyZW1vIHBlcmNlbnRpbCAwIHkgcGVyY2VudGlsIDEwMC4gTG9zIHB1bnRvcyBxdWUgc2UgZW5jdWVudHJhbiBmdWVyYSBkZWwgYmlnb3RlIHNvbiBsb3Mgb3V0bGllcnMuCgogIVtdKGh0dHBzOi8vZGF0YXZpemNhdGFsb2d1ZS5jb20vRVMvbWV0b2Rvcy9pbWFnZXMvYW5hdG9teS9TVkcvZGlhZ3JhbWFfY2FqYXNfeV9iaWdvdGVzLnN2ZykKCkV4cGxvcmVtb3MgbG9zIGRhdG9zIGRlIGRpYW1hbnRlcyBlbnRyZSBlbCBjb3J0ZSB5IGVsIHByZWNpbzoKYGBge3J9CmRhdGExICU+JSBnZ3Bsb3QoKStnZW9tX2JveHBsb3QoYWVzKHggPSBjb3J0ZSwgeT1wcmVjaW8pKQpgYGAKClNpIGxvcyBub21icmVzIGRlIHR1cyB2YXJpYWJsZXMgc29uIG11eSBsYXJnb3MsIGBnZW9tX2JveHBsb3QoKWAgZnVuY2lvbmFyw6EgbWVqb3Igc2kgZ2lyYXMgZWwgZ3LDoWZpY28gZW4gOTDCsC4gUHVlZGVzIGhhY2VyIGVzdG8gYWdyZWdhbmRvIGBjb29yZF9mbGlwKClgICh2b2x0ZWFyIGNvb3JkZW5hZGFzKS4KCmBgYHtyfQpkYXRhMSAlPiUgIGdncGxvdCgpICsgZ2VvbV9ib3hwbG90KGFlcyh4ID0gY29ydGUsIHk9cHJlY2lvKSkrY29vcmRfZmxpcCgpCmBgYAoKIyBSZWxhY2nDs24gZW50cmUgZG9zIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMKClBhcmEgcmVsYWNpb25hciBkb3MgdmFyaWFibGVzIGNhdGVnw7NyaWNhcywgZGViZW1vcyBlbmNvbnRyYXIgdW4gbsO6bWVybyBxdWUgbm9zIHJlbGFjaW9uZSBlbCBjb250ZW8gZW50cmUgZWxsYXMuIEV4aXN0ZW4gZGlmZXJlbnRlcyBmb3JtYXMgZGUgaGFjZXJsbywgdW5hIGRlIGVsbGFzIGVzIGNvbiBsYSBmdW5jacOzbiBgZ2VvbV9jb3VudCgpYCBkZSBsYSBwYXF1ZXRlcsOtYSBkZSBgZ2dwbG90MmAuIERvbmRlIGVsIHRhbWHDsW8gbm9zIGluZGljYSBlbCBjb250ZW8gZGUgbGFzIG9ic2VydmFjaW9uZXMgY29uIGxhIGNvbWJpbmFjacOzbiBkZSB2YXJpYWJsZXMgY29ycmVzcG9uZGllbnRlLiAgCgpgYGB7cn0KZGF0YTEgJT4lIGdncGxvdCgpICsgZ2VvbV9jb3VudChhZXMoeD1jb3J0ZSwgeSA9Y29sb3IpKQpgYGAKClNpIGxvIHF1ZXJlbW9zIHZlciBjb21vIHVuYSB0YWJsYSBkZSBkYXRvcywgdXNhbW9zIGVsIHBhcXVldGUgYGRwbHlyYDoKYGBge3J9CmRhdGExICU+JSBjb3VudChjb2xvcixjb3J0ZSkKYGBgCgpPdHJhIG9wY2nDs24gZXMgdXNhciBsYSB2aXN1YWxpemFjacOzbiBjb24gbGEgZnVuY2nDs24gYGdlb21fdGlsZSgpYCB5IGFkYXB0YXIgdW4gYGZpbGxgIG8gcmVsbGVubyBjb24gZWwgY29udGVvIGBuYCBkZWwgY29udGVvIHByZXZpYW1lbnRlIHJlYWxpemFkby4KYGBge3J9CmRhdGExICU+JSBjb3VudChjb2xvcixjb3J0ZSkgJT4lIAogIGdncGxvdCgpKwogIGdlb21fdGlsZShhZXMoeD1jb2xvcix5PWNvcnRlLCBmaWxsPSBuKSkKYGBgCgojICBSZWxhY2nDs24gZW50cmUgZG9zIHZhcmlhYmxlcyBjb250aW51YXMKCllhIGhhYsOtYW1vcyB2aXN0byBjw7NtbyByZWFsaXphciBkaWFncmFtYXMgZGUgZGlzcGVyc2nDs24gbyBzY2F0dGVycGxvdHMgY29uIGxhIGZ1bmNpw7NuIGBnZW9tX3BvaW50KClgIHkgdmltb3MgcXVlIHBvZGVtb3MgcmVhbGl6YXIgdGFtYmnDqW4gbGEgdGVuZGVuY2lhIHNvbiBmdW5jacOzbiBgZ2VvbV9zbW9vdGgoKWAuICBMb3MgZGlhZ3JhbWFzIGRlIGRpc3BlcnNpw7NuIHJlc3VsdGFuIG1lbm9zIMO6dGlsZXMgYSBtZWRpZGEgbG9zIGRhdG9zIGNyZWNlbiwgcHVlcyBsb3MgcHVudG9zIGVtcGllemFuIGEgc3VwZXJwb25lcnNlIHkgYW1vbnRvbmFyc2UgZW4gw6FyZWFzIG9zY3VyYXMgdW5pZm9ybWVzLCBwYXJhIGVzdG8gcG9kZW1vcyBoYWNlciB1biBwb2NvIGRlIHRyYW5zcGFyZW5jaWEgY29uIGxhIGZ1bmNpw7NuIGBhbHBoYWAKYGBge3J9CmRhdGExICU+JSAgZ2dwbG90KCkrCiAgZ2VvbV9wb2ludChhZXMoeD1xdWlsYXRlLCB5PXByZWNpbyksIGFscGhhID0gMS8xMDApCmBgYAoKT3RyYSBzb2x1Y2nDs24gZXMgbW9kaWZpY2FyIGVsIHBhcsOhbWV0cm8gYmluIChjb250ZW5lZG9yLCBxdWUgZW4gZXN0ZSBjYXNvIGFsdWRlIGEgbGEgaWRlYSBkZSByYW5nbyBvIHVuaWRhZCkuIEFudGVyaW9ybWVudGUgdXNhc3RlIGBnZW9tX2hpc3RvZ3JhbSgpYCB5IGBnZW9tX2ZyZXFwb2x5KClgIHBhcmEgc2VnbWVudGFyIHVuYSB2YXJpYWJsZSBlbiByYW5nb3MgZGUgbWFuZXJhIHVuaWRpbWVuc2lvbmFsLiBBaG9yYSBhcHJlbmRlcsOhcyBhIHVzYXIgYGdlb21fYmluMmQoKWAgeSBgZ2VvbV9oZXgoKWAgcGFyYSBoYWNlcmxvIGVuIGRvcyBkaW1lbnNpb25lcy4KYGBge3J9CmcxIDwtIGRhdGExICU+JSAgZ2dwbG90KCkrCiAgZ2VvbV9iaW4yZChhZXMoeD1xdWlsYXRlLCB5PXByZWNpbykpCiAgCmcyIDwtIGRhdGExICU+JSBnZ3Bsb3QoKSsKICBnZW9tX2hleChhZXMoeD1xdWlsYXRlLHk9cHJlY2lvKSkKCmdnc3RhdHNwbG90Ojpjb21iaW5lX3Bsb3RzKAogIHBsb3RsaXN0ID0gbGlzdChnMSxnMiksCiAgcGxvdGdyaWQuYXJncyA9IGxpc3QobnJvdyA9IDEpLAogIGFubm90YXRpb24uYXJncyA9IGxpc3QoY2FwdGlvbiA9ICJJdGVzbyIpCikKYGBgCgpPdHJhIG9wY2nDs24gZXMgY3JlYXIgaW50ZXJ2YWxvcyBjb24gdW5hIGRlIGxhcyB2YXJpYWJsZXMgY29udGludWFzIGRlIG1hbmVyYSBkZSBxdWUgcHVlZGEgc2VyIHRyYXRhZGEgY29tbyB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EuIEx1ZWdvLCBwdWVkZXMgdXNhciBhbGd1bmEgZGUgbGFzIHTDqWNuaWNhcyBkZSB2aXN1YWxpemFjacOzbiBlbXBsZWFkYXMgcGFyYSByZXByZXNlbnRhciBsYSBjb21iaW5hY2nDs24gZGUgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhIGNvbiB1bmEgdmFyaWFibGUgY29udGludWEuIFBvciBlamVtcGxvLCAKYGBge3J9CmRhdGExICU+JSBnZ3Bsb3QoKSArIAogIGdlb21fYm94cGxvdChhZXMoeCA9IHF1aWxhdGUsIHkgPSBwcmVjaW8sIGdyb3VwID0gY3V0X251bWJlcihxdWlsYXRlLCAyMCkpKQpgYGAKCiMgUmVwb3J0ZSBkZSBleHBsb3JhY2nDs24gZGUgZGF0b3MKCjxjZW50ZXI+CiFbXShodHRwczovL2JveHVhbmN1aS5naXRodWIuaW8vRGF0YUV4cGxvcmVyL3JlZmVyZW5jZS9maWd1cmVzL2xvZ28ucG5nKXt3aWR0aD0xNSV9CjwvY2VudGVyPgoKVGFtYmnDqW4gcG9kZW1vcyByZWFsaXphciByZXBvcnRlcyBjb21wbGV0b3MgY29uIGAgRGF0YUV4cGxvcmVyOjpjcmVhdGVfcmVwb3J0KClgLiBQYXJhIGVsIHNpZ3VpZW50ZSBlamVtcGxvIGRlYmVtb3MgaW5zdGFsYXIgbG9zIHBhcXVldGVzIGBEYXRhRXhwbG9yZXJgICB5IGBueWNmbGlnaHRzMTNgIHBhcmEgbG9zIGRhdG9zIChgaW5zdGFsbC5wYWNrYWdlcygibnljZmxpZ2h0czEzIilgKS4KCmBgYHtyfQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykKbGlicmFyeShEYXRhRXhwbG9yZXIpCmBgYAoKRW4gZWwgcGFxdWV0ZSBgbGlicmFyeShEYXRhRXhwbG9yZXIpYCAgaGF5IDUgZGF0YSBmcmFtZXM6CgoqIGFpcmxpbmVzCgoqIGFpcnBvcnRzCgoqIGZsaWdodHMKCiogcGxhbmVzCgoqIHdlYXRoZXIKClBvZGVtb3MgdmlzdWFsaXphciBzdSBlc3RydWN0dXJhIGRlIGxhIHNpZ3VpZW50ZSBmb3JtYToKCmBgYHtyfQpkYXRhIDwtIGxpc3QoYWlybGluZXMsIGFpcnBvcnRzLCBmbGlnaHRzLCBwbGFuZXMsIHdlYXRoZXIpCnBsb3Rfc3RyKGRhdGEpCmBgYAoKUGFyYSB0ZW5lciB1biBzw7NsbyBkYXRhc2V0IG3DoXMgcm9idXN0byBwb2RlbW9zIGZ1c2lvbmFyIGxhcyB0YWJsYXMgcG9yIG1lZGlvIGRlIGxhIGZ1bmNpw7NuIGBtZXJnZSgpYAoKYGBge3J9CmZpbmFsX2RhdGEgPC0gZmxpZ2h0cyAlPiUgbWVyZ2UoYWlybGluZXMsIGJ5PSAiY2FycmllciIsIGFsbC54ID0gVFJVRSkgJT4lIAogIG1lcmdlKHBsYW5lcywgYnkgPSAidGFpbG51bSIsIGFsbC54ID0gVFJVRSwgc3VmZml4ZXMgPSBjKCJfZmxpZ2h0cyIsICJfcGxhbmVzIikpICU+JSAKICBtZXJnZShhaXJwb3J0cywgYnkueCA9ICJvcmlnaW4iLCBieS55ID0gImZhYSIsIGFsbC54ID0gVFJVRSwgc3VmZml4ZXMgPSBjKCJfY2FycmllciIsICJfb3JpZ2luIikpICU+JSAKICBtZXJnZShhaXJwb3J0cywgYnkueCA9ICJkZXN0IiwgYnkueSA9ICJmYWEiLCBhbGwueCA9IFRSVUUsIHN1ZmZpeGVzID0gYygiX29yaWdpbiIsICJfZGVzdCIpKQpgYGAKCiMjIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGUgZGF0b3MgY29uIGxhIHBhcXVldGVyw61hIGBEYXRhRXhwbG9yZXJgCgpQYXJhIGNvbm9jZXIgZWwgY29uanVudG8gZGUgZGF0b3MgcG9kZW1vcyByZWFsaXphciB1biBgc3VtbWFyeSgpYCBjb21vIGxvIGhpY2ltb3MgZW4gbGEgc2VzacOzbiBhbnRlcmlvciBvIHBvZGVtb3MgdXRpbGl6YXIgbGEgZnVuY2nDs24gYGludHJvZHVjZSgpYC4KCmBgYHtyfQppbnRyb2R1Y2UoZmluYWxfZGF0YSkKCiMgRGUgZm9ybWEgZ3LDoWZpY2EKcGxvdF9pbnRybyhmaW5hbF9kYXRhKQpgYGAKCkRlYmVtb3MgZGUgbm90YXIgYWxnbyBlbiBlc3RlIGNvbmp1bnRvIGRlIGRhdG9zOgoKKiBTw7NsbyBlbCAwLjI3JSBkZSBsYXMgZmlsYXMgZXN0w6FuIGNvbXBsZXRhcywKCiogdGVuZW1vcyA1LjclIGRlIG9ic2VydmFjaW9uZXMgZmFsdGFudGVzLCBlcyBkZWNpciwgZGFkbyBxdWUgc29sbyB0ZW5lbW9zIDAuMjclIGRlIGxhcyBmaWxhcyBjb21wbGV0YXMsIHNvbG8gaGF5IDUuNyUgZGUgb2JzZXJ2YWNpb25lcyBmYWx0YW50ZXMgZGVsIHRvdGFsLgoKRXN0b3MgdmFsb3JlcyBmYWx0YW50ZXMgbm9zIHBvZHLDoW4gZ2VuZXJhbCBwcm9ibGVtYXMgcGFyYSBhbmFsaXphciBsb3MgZGF0b3MsIHZlYW1vcyB1biBwb2NvIGxvcyBwZXJmaWxlcyBxdWUgZmFsdGFuLgoKIyMjIFZhbG9yZXMgZmFsdGFudGVzIChtaXNzaW5nIHZhbHVlcykKClNpZW1wcmUsIGVuIHRvZG9zIGxvcyBwcm9ibGVtYXMgcmVhbGVzLCB2YW1vcyBhIHRlbmVyIGRhdG9zIGRlc29yZGVuYWRvcyB5IGNvbiBwcm9ibGVtYXMuIFBhcmEgdmlzdWFsaXphciBlbCBwZXJmaWwgZGUgbG9zIGRhdG9zIGZhbHRhbnRlcyBwb2RlbW9zIHV0aWxpemFyIGxhIGZ1bmNpw7NuIGBwbG90X21pc3NpbmcoKWAuCgpgYGB7cn0KcGxvdF9taXNzaW5nKGZpbmFsX2RhdGEpCmBgYAoKU2kgbGUgZ3VzdGEgbcOhcyB0ZW5lciBsYSBpbmZvcm1hY2nDs24gZW4gZm9ybWEgZGUgdGFibGEgcHVlZGUgb2J0ZW5lciBzdSBwZXJmaWwgcG9yIG1lZGlvIGRlIGxhIGZ1bmNpw7NuIGBwcm9maWxlX21pc3NpbmcoZmluYWxfZGF0YSlgLiAKCkVuIGxhIHZpc3VhbGl6YWNpw7NuIGFudGVyaW9yLCBwb2RlbW9zIHZlciBxdWUgbGEgdmFyaWFibGUgYHNwZWVkYCBlcyBsYSBxdWUgZW4gc3UgbWF5b3LDrWEgbGUgZmFsdGEgaW5mb3JtYWNpw7NuLCBhbCBwYXJlY2VyIGVuY29udHJhbW9zIGVsIGN1bHBhYmxlIGRlIHF1ZSBzw7NsbyBlbCAwLjI3JSBkZSBudWVzdHJhcyBmaWxhcyBlc3TDqW4gY29tcGxldGFzIHkgcHJvYmFibGVtZW50ZSBlc3RhIHZhcmlhYmxlIG5vIHNlYSBkZSBtdWNoYSBpbmZvcm1hY2nDs24uIFBvciB0YW50byBsYSBwb2RlbW9zIGVsaW1pbmFyIGRlIG51ZXN0cm8gZGF0YWZyYW1lLgoKYGBge3J9CmZpbmFsX2RhdGEgPC0gZHJvcF9jb2x1bW5zKGZpbmFsX2RhdGEsICJzcGVlZCIpCmBgYAoKIyMjIERpc3RyaWJ1Y2lvbmVzCgpMYSB2aXN1YWxpemFjacOzbiBkZSBsYXMgZGlzdHJpYnVjaW9uZXMgZGUgZnJlY3VlbmNpYSBwYXJhIHRvZGFzIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRpc2NyZXRhcyBzZSBwdWVkZSByZWFsaXphciBjb24gbGEgZnVuY2nDs24gYHBsb3RfYmFyKClgLgoKYGBge3J9CnBsb3RfYmFyKGZpbmFsX2RhdGEpCmBgYAoKVHJhcyB1bmEgaW5zcGVjY2nDs24gZGV0YWxsYWRhIGRlIGxhIHZhcmlhYmxlIGBtYW51cmFjdHVyZXJgLCBubyBlcyBmw6FjaWwgaWRlbnRpZmljYXIgbGFzIHNpZ3VpZW50ZXMgY2FyYWN0ZXLDrXN0aWNhcyBkdXBsaWNhZGFzOgoKKiBBSVJCVVMgYW5kIEFJUkJVUyBJTkRVU1RSSUUKCiogQ0FOQURBSVIgYW5kIENBTkFEQUlSIExURAoKKiBNQ0RPTk5FTEwgRE9VR0xBUywgTUNET05ORUxMIERPVUdMQVMgQUlSQ1JBRlQgQ08gYW5kIE1DRE9OTkVMTCBET1VHTEFTIENPUlBPUkFUSU9OCgpQb3IgdGFudG8sIGRlYmVtb3MgcHJvY2VkZXIgYSBsaW1waWFyIGVzdGEgdmFyaWFibGUKCmBgYHtyfQpmaW5hbF9kYXRhW3doaWNoKGZpbmFsX2RhdGEkbWFudWZhY3R1cmVyID09ICJBSVJCVVMgSU5EVVNUUklFIiksXSRtYW51ZmFjdHVyZXIgPC0gIkFJUkJVUyIKZmluYWxfZGF0YVt3aGljaChmaW5hbF9kYXRhJG1hbnVmYWN0dXJlciA9PSAiQ0FOQURBSVIgTFREIiksXSRtYW51ZmFjdHVyZXIgPC0gIkNBTkFEQUlSIgpmaW5hbF9kYXRhW3doaWNoKGZpbmFsX2RhdGEkbWFudWZhY3R1cmVyICVpbiUgYygiTUNET05ORUxMIERPVUdMQVMgQUlSQ1JBRlQgQ08iLCAiTUNET05ORUxMIERPVUdMQVMgQ09SUE9SQVRJT04iKSksXSRtYW51ZmFjdHVyZXIgPC0gIk1DRE9OTkVMTCBET1VHTEFTIgoKcGxvdF9iYXIoZmluYWxfZGF0YSRtYW51ZmFjdHVyZXIpCgpgYGAKQWRpY2lvbmFsbWVudGUsIGxhcyB2YXJpYWJsZXMgYGRzdF9vcmlnaW5gLCBgdHpvbmVfb3JpZ2luYCwgYHllYXJfZmxpZ2h0c2AgeSBgdHpfb3JpZ2luYCBjb250aWVuZW4gdW4gc29sbyB2YWxvciwgcG9yIGxvIHF1ZSBkZWJlcsOtYW1vcyBwcm9jZWRlciBhIGVsaW1pbmFybGEsIHlhIHF1ZSBubyBub3MgcHJvcG9yY2lvbmEgaW5mb3JtYWNpw7NuOgoKYGBge3J9CmZpbmFsX2RhdGEgPC0gZHJvcF9jb2x1bW5zKGZpbmFsX2RhdGEsIGMoImRzdF9vcmlnaW4iLCAidHpvbmVfb3JpZ2luIiwgInllYXJfZmxpZ2h0cyIsICJ0el9vcmlnaW4iKSkKYGBgCgpDb24gZnJlY3VlbmNpYSwgZXMgbXV5IGJlbmVmaWNpb3NvIG9ic2VydmFyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgZnJlY3VlbmNpYSBiaXZhcmlhZGEuIFBvciBlamVtcGxvLCBwYXJhIHZlciBjYXJhY3RlcsOtc3RpY2FzIGRpc2NyZXRhcyBwb3IgYGFycl9kZWxheWA6CgpgYGB7cn0KcGxvdF9iYXIoZmluYWxfZGF0YSwgd2l0aCA9ICJhcnJfZGVsYXkiKQpgYGAKCk7Ds3Rlc2UgcXVlIGxhIGRpc3RyaWJ1Y2nDs24gcmVzdWx0YW50ZSBzZSB2ZSBiYXN0YW50ZSBkaWZlcmVudGUgZGUgbGEgZGlzdHJpYnVjacOzbiBkZSBmcmVjdWVuY2lhcyByZWd1bGFyLgoKUHVlZGUgb3B0YXIgcG9yIGRpdmlkaXIgcG9yIGNvbG9yZXMgdG9kYXMgbGFzIGZyZWN1ZW5jaWFzIHBvciB1bmEgdmFyaWFibGUgZGlzY3JldGEsIGNvbW8gcG9yIGVqZW1wbG8gYG9yaWdpbmA6CgpgYGB7cn0KcGxvdF9iYXIoZmluYWxfZGF0YSwgYnkgPSAib3JpZ2luIikKYGBgCgojIyMgSGlzdG9ncmFtYXMKClBhcmEgdmlzdWFsaXphciBkaXN0cmlidWNpb25lcyBkZSB0b2RhcyBsYXMgdmFyaWFibGVzIGNvbnRpbnVhcyBwb2RlbW9zIHV0aWxpemFyIGxhIGZ1bmNpw7NuICBgcGxvdF9oaXN0b2dyYW0oKWA6CgpgYGB7cn0KcGxvdF9oaXN0b2dyYW0oZmluYWxfZGF0YSkKYGBgCgpUYW1iacOpbiBwb2RlbW9zIG9ic2VydmFyIHF1ZSBoYXkgdmFyaWFibGVzIGRlIGZlY2hhcyB5IGhvcmFzIHF1ZSBkZWJlbiB0cmF0YXJzZSB1biBwb2NvIG3DoXMsIHBvciBlamVtcGxvLCBjb25jZW50cmFuZG8gYcOxbywgbWVzLCBkw61hIHBhcmEgZm9ybWFyIHVuYSB2YXJpYWJsZSBkZSBgZmVjaGFgIHkvbyBhZ3JlZ2FyIGhvcmEgeSBtaW51dG8gcGFyYSBmb3JtYXIgbGEgdmFyaWFibGUgYGZlY2hhX2hvcmFgLgoKT3RyYSBwYXJ0ZSBxdWUgcG9kZW1vcyBsaW1waWFyLCBlcyBwb3IgZWplbXBsbyBsYSB2YXJpYWJsZSBgZmxpZ2h0YCwgeWEgcXVlIGVzYSBkZWJlcsOtYW1vcyB0ZW5lcmxhIGNvbW8gdW4gZmFjdG9yLCBwb3Igc2VyIHVuIG7Dum1lcm8gZGUgdnVlbG8geSBudW3DqXJpY2FtZW50ZSBubyB0aWVuZSBuaW5nw7puIHNpZ25pZmljYWRvOgoKYGBge3J9CmZpbmFsX2RhdGEgPC0gdXBkYXRlX2NvbHVtbnMoZmluYWxfZGF0YSwgImZsaWdodCIsIGFzLmZhY3RvcikKYGBgCgpUYW1iacOpbiBwb2RlbW9zIHJlbW92ZXIgbGFzIHZhcmlhYmxlcyBgIHllYXJfZmxpZ2h0c2AgeSBgIHR6X29yaWdpbmAgeWEgcXVlIHPDs2xvIGNvbnRpZW5lbiB1biB2YWxvcjoKCmBgYHtyfQpmaW5hbF9kYXRhIDwtIGRyb3BfY29sdW1ucyhmaW5hbF9kYXRhLCBjKCJ5ZWFyX2ZsaWdodHMiLCAidHpfb3JpZ2luIikpCmBgYAoKIyMjIFFRIHBsb3QgCgpMYSBncsOhZmljYSAqUXVhbnRpbGUtUXVhbnRpbGUqIGVzIHVuYSBmb3JtYSBkZSB2aXN1YWxpemFyIGxhIGRlc3ZpYXNpw7NuIGRlIHVuYSBkaXN0cmlidWNpw7NuIGRlIHByb2JhYmlsaWRhZCBlc3BlY8OtZmljYS4gIAoKRGVzcHXDqXMgZGUgYW5hbGl6YXIgZXN0b3MgZ3LDoWZpY29zLCBhIG1lbnVkbyBlcyBiZW5lZmljaW9zbyBhcGxpY2FyIHVuYSB0cmFuc2Zvcm1hY2nDs24gbWF0ZW3DoXRpY2EgKGNvbW8gbG9nYXJpdG1vKSBwYXJhIG1vZGVsb3MgY29tbyBsYSByZWdyZXNpw7NuIGxpbmVhbC4gUGFyYSBoYWNlcmxvLCBwb2RlbW9zIHVzYXIgbGEgZnVuY2nDs24gYHBsb3RfcXFgLiBEZSBmb3JtYSBwcmVkZXRlcm1pbmFkYSwgc2UgY29tcGFyYSBjb24gbGEgZGlzdHJpYnVjacOzbiBub3JtYWwuCgpOb3RhOiBMYSBmdW5jacOzbiBsbGV2YXLDoSBtdWNobyB0aWVtcG8gY29uIG11Y2hhcyBvYnNlcnZhY2lvbmVzLCBwb3IgbG8gcXVlIHB1ZWRlIG9wdGFyIHBvciBlc3BlY2lmaWNhciB1biBgc2FtcGxlZF9yb3dzYCBhcHJvcGlhZG86CgpgYGB7cn0KcXFfZGF0YSA8LSBmaW5hbF9kYXRhWywgYygiYXJyX2RlbGF5IiwgImFpcl90aW1lIiwgImRpc3RhbmNlIiwgInNlYXRzIildCgpwbG90X3FxKHFxX2RhdGEsIHNhbXBsZWRfcm93cyA9IDEwMDBMKQpgYGAKCkVuIGVsIGdyw6FmaWNvLCBgYWlyX3RpbWVgLCBgZGlzdGFuY2VgIHkgYXNpZW50b3MgcGFyZWNlbiBzZXNnYWRvcyBlbiBhbWJhcyBjb2xhcy4gQXBsaXF1ZW1vcyB1bmEgdHJhbnNmb3JtYWNpw7NuIGxvZ2Fyw610bWljYSBzaW1wbGUgeSBncmFmaXF1ZW1vcyBkZSBudWV2by4KCmBgYHtyfQpsb2dfcXFfZGF0YSA8LSB1cGRhdGVfY29sdW1ucyhxcV9kYXRhLCAyOjQsIGZ1bmN0aW9uKHgpIGxvZyh4ICsgMSkpCgpwbG90X3FxKGxvZ19xcV9kYXRhWywgMjo0XSwgc2FtcGxlZF9yb3dzID0gMTAwMEwpCmBgYAoKQ29uIGVzdG8gb2J0ZW5lciB1bmEgbWVqb3IgZGlzdHJpYnVjacOzbi4gU2kgZXMgbmVjZXNhcmlvLCB0YW1iacOpbiBwdWVkZSB2ZXIgZWwgZ3LDoWZpY28gUVEgbWVkaWFudGUgb3RyYSBmdW5jacOzbjoKCmBgYHtyfQpxcV9kYXRhIDwtIGZpbmFsX2RhdGFbLCBjKCJuYW1lX29yaWdpbiIsICJhcnJfZGVsYXkiLCAiYWlyX3RpbWUiLCAiZGlzdGFuY2UiLCAic2VhdHMiKV0KCnBsb3RfcXEocXFfZGF0YSwgYnkgPSAibmFtZV9vcmlnaW4iLCBzYW1wbGVkX3Jvd3MgPSAxMDAwTCkKYGBgCgojIyMgQ29ycmVsYXRpb24gQW5hbHlzaXMKClBhcmEgdmlzdWFsaXphciBlbCBtYXBhIGRlIGNhbG9yIGRlIGxhIGNvcnJlbGFjacOzbiBkZSB0b2RhcyBsYXMgdmFyaWFibGVzIHF1ZSBubyB0ZW5nYW4gZGF0b3MgZmFsdGFudGVzIGxvIHBvZGVtb3MgcmVhbGl6YXIgZGUgbGEgc2lndWllbnRlIGZvcm1hOgoKYGBge3J9CnBsb3RfY29ycmVsYXRpb24obmEub21pdChmaW5hbF9kYXRhKSwgbWF4Y2F0ID0gNUwpCmBgYAoKVGFtYmnDqW4gcHVlZGUgZ3JhZmljYXIgdmFyaWFibGVzIHPDs2xvIGRpc2NyZXRhcy9jb250aW51YXMgZGUgbGEgc2lndWllbnRlIGZvcm1hOgoKYGBge3J9CnBsb3RfY29ycmVsYXRpb24obmEub21pdChmaW5hbF9kYXRhKSwgdHlwZSA9ICJjIikKcGxvdF9jb3JyZWxhdGlvbihuYS5vbWl0KGZpbmFsX2RhdGEpLCB0eXBlID0gImQiKQpgYGAKCiMjIyBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpCgpFbCBhbsOhbGlzaXMgZGUgY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgKFBDQSwgcG9yIHN1cyBzaWdsYXMgZW4gaW5nbMOpcywpIGNvbnNpc3RlIGVuIGV4cHJlc2FyIHVuIGNvbmp1bnRvIGRlIHZhcmlhYmxlcyBlbiB1biBjb25qdW50byBkZSBjb21iaW5hY2lvbmVzIGxpbmVhbGVzIGRlIGZhY3RvcmVzIG5vIGNvcnJlbGFjaW9uYWRvcyBlbnRyZSBzw60sIGVzdG9zIGZhY3RvcmVzIGRhbmRvIGN1ZW50YSB1bmEgZnJhY2Npw7NuIGNhZGEgdmV6IG3DoXMgZMOpYmlsIGRlIGxhIHZhcmlhYmlsaWRhZCBkZSBsb3MgZGF0b3MuCgpFc3RlIGFuw6FsaXNpcyBsbyBwb2RlbW9zIHJlYWxpemFyIGRpcmVjdGFtZW50ZSBjb24gbGEgZnVuY2nDs24gYHBsb3RfcHJjb21wIChuYS5vbWl0IChmaW5hbF9kYXRhKSlgLCBwZXJvIFBDQSBmdW5jaW9uYXLDoSBtZWpvciBzaSBsaW1waWFtb3MgbG9zIGRhdG9zIHByaW1lcm86CgpgYGB7cn0KcGNhX2RmIDwtIG5hLm9taXQoZmluYWxfZGF0YVssIGMoIm9yaWdpbiIsICJkZXBfZGVsYXkiLCAiYXJyX2RlbGF5IiwgImFpcl90aW1lIiwgInllYXJfcGxhbmVzIiwgInNlYXRzIildKQoKcGxvdF9wcmNvbXAocGNhX2RmLCB2YXJpYW5jZV9jYXAgPSAwLjksIG5yb3cgPSAyTCwgbmNvbCA9IDJMKQpgYGAKCiMjIyBCb3hwbG90cwoKU3Vwb25nYSBxdWUgbGUgZ3VzdGFyw61hIGNvbnN0cnVpciB1biBtb2RlbG8gcGFyYSBwcmVkZWNpciBsb3MgcmV0cmFzb3MgZW4gbGFzIGxsZWdhZGFzLCBwdWVkZSB2aXN1YWxpemFyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgdG9kYXMgbGFzIGNhcmFjdGVyw61zdGljYXMgY29udGludWFzIGVuIGZ1bmNpw7NuIGRlIGxvcyByZXRyYXNvcyBlbiBsYXMgbGxlZ2FkYXMgY29uIHVuIGRpYWdyYW1hIGRlIGNhamEvYmlnb3RlcyAoYm94cGxvdCk6CgpgYGB7cn0KIyMgUmVkdWNlIGRhdGEgc2l6ZSBmb3IgZGVtbyBwdXJwb3NlCmFycl9kZWxheV9kZiA8LSBmaW5hbF9kYXRhWywgYygiYXJyX2RlbGF5IiwgIm1vbnRoIiwgImRheSIsICJob3VyIiwgIm1pbnV0ZSIsICJkZXBfZGVsYXkiLCAiZGlzdGFuY2UiLCAieWVhcl9wbGFuZXMiLCAic2VhdHMiKV0KCiMjIENhbGwgYm94cGxvdCBmdW5jdGlvbgpwbG90X2JveHBsb3QoYXJyX2RlbGF5X2RmLCBieSA9ICJhcnJfZGVsYXkiKQpgYGAKCkVudHJlIHRvZG9zIGxvcyBjYW1iaW9zIHN1dGlsZXMgZW4gY29ycmVsYWNpw7NuIGNvbiBsb3MgcmV0cmFzb3MgZW4gbGFzIGxsZWdhZGFzLCBzZSBwdWVkZSBkZXRlY3RhciBpbm1lZGlhdGFtZW50ZSBxdWUgbG9zIGF2aW9uZXMgY29uIG3DoXMgZGUgMzAwIGFzaWVudG9zIHRpZW5kZW4gYSB0ZW5lciByZXRyYXNvcyBtdWNobyBtw6FzIGxhcmdvcyAoMTYgfiAyMSBob3JhcykuICpBaG9yYSBwb2RlbW9zIHByb2Z1bmRpemFyIG3DoXMgcGFyYSB2ZXJpZmljYXIgbyBnZW5lcmFyIG3DoXMgaGlww7N0ZXNpcy4qCgojIyMgU2NhdHRlcnBsb3RzCgpQYXJhIG1pcmFyIGxhcyByZWxhY2lvbmVzIGVudHJlIHZhcmlhYmxlcyBwb2RlbW9zIHZpc3VhbGl6YXIgc2NhdHRlcnBsb3RzIG8gZGlhZ3JhbWFzIGRlIGRpc3BlcnNpw7NuLgoKYGBge3J9CmFycl9kZWxheV9kZjIgPC0gZmluYWxfZGF0YVssIGMoImFycl9kZWxheSIsICJkZXBfdGltZSIsICJkZXBfZGVsYXkiLCAiYXJyX3RpbWUiLCAiYWlyX3RpbWUiLCAiZGlzdGFuY2UiLCAieWVhcl9wbGFuZXMiLCAic2VhdHMiKV0KCnBsb3Rfc2NhdHRlcnBsb3QoYXJyX2RlbGF5X2RmMiwgYnkgPSAiYXJyX2RlbGF5Iiwgc2FtcGxlZF9yb3dzID0gMTAwMEwpCmBgYAoKUGFyYSBmaW5hbGl6YXIsIHNpIHF1ZXJlbW9zIHF1ZSBzZSBoYWdhIHVuIHJlcG9ydGUgYXV0b23DoXRpY28sIGxvIHBvZGVtb3MgcmVhbGl6YXIgY29uIGxhIGZ1bmNpw7NuIGBjcmVhdGVfcmVwb3J0KClgLCBsbyBxdWUgbm9zIGRhcsOhIGNvbW8gcmVzdWx0YWRvIHVuIGh0bWwgY29uIHRvZG8gbG8gcXVlIGNvcnJlc3BvbmRlIGEgdW4gYW7DoWxpc2lzIHLDoXBpZG8gZGUgZXhwbG9yYWNpw7NuIGRlIGRhdG9zLCBwZXJvIG9qbywgZXMgbcOhcyBjb252ZW5pZW50ZSBpcmxvIGhhY2llbmRvIHBhc28gYSBwYXNvIHBhcmEgcXVlIHB1ZWRhcyBpciBlbnRlbmRpZW5kbyB5IGxpbXBpYW5kbyB0dXMgZGF0b3Mu